// 日志文件
#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <string>
#include <bits/fs_fwd.h>
#include <filesystem> //C++17  文件操作相关方法
#include <fstream>    //C++ 文件操作  文件流
#include <memory>     //使用智能指针（不需要考虑释放的问题，接口设计直接Enable，不担心内存泄露的问题）
#include <unistd.h>   //pid 的头文件
#include <sstream>    //拼接
#include<ctime>       //时间戳  头文件
#include<cstdio>
#include "Mutex.hpp"

//  日志的功能
// 	1.形成完整日志
// 	2.刷新到目标文件（打印到显示器，打印到指定文件）      想要定制化 ---> 策略模式

/*
    可以通过定义基类的方式来规定统一接口，由不同的子类充当一种不同形式的策略，这种设计模式叫做策略模式！！！
    利用的就是C++的多态特性！！！
*/

// 日志命名空间
namespace LogModule
{
    // 刷新策略：1.显示器打印     2.往指定的文件写入
    using namespace MutexModule;

    const std::string gsep = "\r\n"; // 对应的分隔符，回车换行

    // 刷新策略基类
    class LogStrategy // 日志策略类
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0; // 传一条完整的日志信息
    };

    // 子类   继承基类
    // 显示器打印的策略！   子类
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {
        }

        void SyncLog(const std::string &message) override
        {
            // 想把内容 message 打印到显示器上   需要加锁
            LockGuard lockguard(_mutex);
            std::cout << message << gsep;
        }

        ~ConsoleLogStrategy()
        {
        }

    private:
        Mutex _mutex;
    };

    // 文件打印日志的策略 ：子类
    const std::string defaultpath = "./log";
    const std::string defaultfile = "my.log";
    class FileLogStrategy : public LogStrategy
    {
    public:
        // 构造日志类刷新策略
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path),
              _file(file)
        {
            // 加锁
            LockGuard lockguard(_mutex);

            // 判断路径是否存在（存在就什么都不做，不存在就要新建）
            if (std::filesystem::exists(_path)) // filesystem C++17判断文件是否存在
            {
                return; // 存在就直接返回
            }
            try
            {
                std::filesystem::create_directories(_path); // 不存在就创建
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }

        // 刷新策略
        void SyncLog(const std::string &message) override
        {
            // 流式方式写

            // 加锁
            LockGuard lockguard(_mutex);

            // ".log" + "my.log"    形成具有完整路径的文件名    进行判断，防止不是一个完整路径
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            std::ofstream out(filename, std::ios::app); // 这表示的是  追加写入的方式进行打开
            if (!out.is_open())
            {
                return;
            }
            out << message << gsep;
            out.close();
        }

        ~FileLogStrategy()
        {
        }

    private:
        std::string _path; // 日志文件所在路径
        std::string _file; // 日志文件本身
        Mutex _mutex;
    };

    // 上面是策略模式，形成一条完整的日志 && 根据上面的策略，选择不同的刷新方式
    // 用枚举来定义日志等级（形成日志等级）
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string Level2Str(LogLevel level)
    {
        switch(level)
        {
            case LogLevel::DEBUG:
                return "DEBUG";
            case LogLevel::INFO:
                return "INFO";
            case LogLevel::WARNING:
                return "WARNING";
            case LogLevel::ERROR:
                return "ERROR";
            case LogLevel::FATAL:
                return "FATAL";
            default:
                return "UNKNOWN";     //表示未知
        }
    }
    

///////////////////////////////////////////  时间获取   ///////////////////////////////////////
    // 获取当前的时间
    std::string GetTimeStamp()
    {
    //获取时间格式
        time_t curr = time(nullptr);      
        struct tm curr_tm;    
        localtime_r(&curr,&curr_tm);    //拿到年月日时分秒

    //定义一个缓冲区
        char timebuffer[128];
    //注意：年份要减去1900，如果恢复正常就要+1900;月份+1;
        snprintf(timebuffer,sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d",      //按照这个格式打印到缓冲区
                                                curr_tm.tm_year+1900,
                                                curr_tm.tm_mon+1,
                                                curr_tm.tm_mday,
                                                curr_tm.tm_hour,
                                                curr_tm.tm_min,
                                                curr_tm.tm_sec
    );
        return timebuffer;       //返回缓冲区，形成临时拷贝，构建字符串
    }
///////////////////////////////////////////  时间获取   ///////////////////////////////////////


    // 日志类
    // 1.按照用户需求传入的参数形成日志     2.根据不同的策略模式，完成刷新
    class Logger
    {
    public:
        Logger()
        {
            EnableConsoleStrategy(); // 默认是向显示器打
        }

        void EnableFileLogStrategy() // 文件策略刷新    指针指针修改指向，老资源被自动释放
        {
            _fflush_strategy = std::make_unique<FileLogStrategy>();
        }

        void EnableConsoleStrategy() // 显示器策略刷新
        {
            _fflush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        // 内部类，表示未来的一条日志
        // 在内部建立一个类是为了支持运算符重载和支持可变参数的！！！！
        class LogMessage
        {
        public:
            LogMessage(LogLevel &level, std::string &src_name, int line_number,Logger &logger)
                : _curr_time(GetTimeStamp()),
                  _level(level),
                  _pid(getpid()),
                  _src_name(src_name),
                  _line_number(line_number),
                  _logger(logger)
            {
                // 日志的左边部分  合并起来
                std::stringstream ss;
                ss << "[" << _curr_time << "]"
                   << "[" << Level2Str(_level) << "]"    //这里的Level不能直接用_level，会出问题，加一层Level2Str
                   << "[" << _pid << "]"
                   << "[" << _src_name << "]"
                   << "[" << _line_number << "]"
                   <<"-";

                //ss 是写到了流里面，需要提取出来并给_loginfo
                _loginfo = ss.str();       //ss流中提供的有str方法
            }


    // LogMessage() << "hello world" << "xxxx" << 3.14 <<1234    
            //右半部分
            template<typename T>
            LogMessage &operator << (const T &info)   //引用返回，是因为不是只格式化输出一个而是很多个
            {
                //日志的右半部分，是可变的
                std::stringstream ss;      //先拼接到右边
                ss << info;
                _loginfo += ss.str();      //拼出来消息
                return *this;         
            }

            ~LogMessage()
            {
            //把外部对象Logger拉进来的目的（析构的时候刷新，外部类具有刷新策略)
                if(_logger._fflush_strategy)     
                {
                    _logger._fflush_strategy->SyncLog(_loginfo);   //析构的时候把一条message刷新到显示器
                }
            }

        private:
            std::string _curr_time; // 当前日志的时间
            LogLevel _level;        // 日志等级
            pid_t _pid;             // 属于哪个进程
            std::string _src_name;  // 源文件名
            int _line_number;       // 行号
            std::string _loginfo;   // 日志的完整信息（内容）     合并之后，一条完整的信息
            Logger &_logger;         //logger对象的引用    
        };


    //仿函数(在外部的Logger类里面   重载了一下它的()   未来需要传递：日志等级，文件名，行号)
    //故意写成返回临时对象   
    //临时对象，只要运算符 没有结束，就一直存在，参数用完，输出结束，临时对象会自动释放，就会调用析构，自动刷新

    //一旦调用operator的时候，就会创建出LogMessage，LogMessage就会走到输出运算符重载，调用operator<<
    //调用的时候就可以把参数获取进来了
        LogMessage operator()(LogLevel level,std::string name,int line)
        {
            //参数传进来的分别是：日志等级，文件名，行号
            return LogMessage(level,name,line,*this);
            //调用的是LogMessage的构造函数
        }

        ~Logger()
        {
        }

    private:
        std::unique_ptr<LogStrategy> _fflush_strategy;
    };

//全局日志对象
    Logger logger;

//使用宏，简化用户的操作，获取文件名和行号
    #define LOG(level) logger(level,__FILE__,__LINE__)             //c++有预处理符
    #define Enable_Console_Log_Strategy() logger.EnableConsoleStrategy()     //开启显示器策略
    #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()      //开启文件策略
//有了这三个宏，未来调用日志 可以选
//C++ 预处理符   __FILE__  __LINE__
//替换完成之后，目标文件单位文件名（Main.cc），行号
//替换之后就变成这样   logger(LogLevel::DEBUG,"main.cc",10) << "hello world";



}

#endif
